/*

Copyright (C) 2000 - 2008 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#include <nd.h>
#include <nd_gui.h>

#include <nd_icmp.h>
#include <nd_icmp_callbacks.h>


static ND_ProtoField icmp_fields[] = {
  { ND_VAL_FIELD, N_("Type (%s)"),         N_("ICMP message type"),  8, nd_icmp_type_cb   },
  { ND_VAL_FIELD, N_("Code (%s)"),         N_("ICMP message code"),  8, nd_icmp_code_cb   },
  { ND_VAL_FIELD, N_("Checksum (0x%.4x)"), N_("ICMP checksum"),     16, nd_icmp_cksum_cb  },
  { 0, NULL, NULL, 0, NULL }
};

static ND_ProtoField icmp_req_seq_fields[] = {
  { ND_VAL_FIELD, N_("ID (%u)"),           N_("Request identifier"), 16, nd_icmp_id_cb  },
  { ND_VAL_FIELD, N_("Seq. (%u)"),         N_("Sequence number"),    16, nd_icmp_seq_cb },
  { 0, NULL, NULL, 0, NULL }
};

static ND_ProtoField icmp_addr_mask_fields[] = {
  { ND_VAL_FIELD, N_("Subnet mask (%s)"),  N_("IP subnet mask"),     32, nd_icmp_ip_cb },
  { 0, NULL, NULL, 0, NULL }
};

static ND_ProtoField icmp_tstamp_fields[] = {
  { ND_VAL_FIELD, N_("Orig. TS (%lu)"),     N_("Originate timestamp"), 32, nd_icmp_ts_orig_cb },
  { ND_VAL_FIELD, N_("Recv. TS (%lu)"),     N_("Receive timestamp"),   32, nd_icmp_ts_recv_cb },
  { ND_VAL_FIELD, N_("Trans. TS (%lu)"),    N_("Transmit timestamp"),  32, nd_icmp_ts_trans_cb },
  { 0, NULL, NULL, 0, NULL }
};

static ND_ProtoField icmp_error_fields[] = {
  { ND_VAL_FIELD, N_("Data (%s)"),          N_("Data field"),          32, nd_icmp_error_cb },
  { 0, NULL, NULL, 0, NULL }
};

static ND_ProtoField icmp_router_adv_fields[] = {
  { ND_VAL_FIELD, N_("Number of addrs. (%u)"),     N_("Number of addresses"),      8,  nd_icmp_adv_num_addr_cb },
  { ND_VAL_FIELD, N_("Entry size (%u)"),           N_("Address entry size"),       8,  nd_icmp_adv_addr_entry_cb },
  { ND_VAL_FIELD, N_("Lifetime (%u s)"),           N_("Address lifetime"),        16,  nd_icmp_adv_lifetime_cb },
  { ND_VAL_FIELD, N_("Router address (%s)"),       N_("Router address"),          32,  nd_icmp_adv_ip_cb },
  { ND_VAL_FIELD, N_("Pref. level (0x%.8x)"),      N_("Preference level"),        32,  nd_icmp_adv_pref_cb },
  { 0, NULL, NULL, 0, NULL }
};

ND_MenuData icmp_menu_type_data[] = {
  { "Echo reply", NULL,                           ICMP_ECHOREPLY,      nd_icmp_type_value_cb },
  { "Destination unreachable", NULL,              ICMP_DEST_UNREACH,   nd_icmp_type_value_cb },
  { "Source quench", NULL,                        ICMP_SOURCE_QUENCH,  nd_icmp_type_value_cb },
  { "Redirect", NULL,                             ICMP_REDIRECT,       nd_icmp_type_value_cb },
  { "Echo request", NULL,                         ICMP_ECHO,           nd_icmp_type_value_cb },
  { "Router advertisement", NULL,                 9,                   nd_icmp_type_value_cb },
  { "Router solicitation", NULL,                  10,                  nd_icmp_type_value_cb },
  { "Time exceeded", NULL,                        ICMP_TIME_EXCEEDED,  nd_icmp_type_value_cb },
  { "Parameter problem", NULL,                    ICMP_PARAMETERPROB,  nd_icmp_type_value_cb },
  { "Timestamp request", NULL,                    ICMP_TIMESTAMP,      nd_icmp_type_value_cb },
  { "Timestamp reply", NULL,                      ICMP_TIMESTAMPREPLY, nd_icmp_type_value_cb },
  { "Information request", NULL,                  ICMP_INFO_REQUEST,   nd_icmp_type_value_cb },
  { "Information reply", NULL,                    ICMP_INFO_REPLY,     nd_icmp_type_value_cb },
  { "Address request", NULL,                      ICMP_ADDRESS,        nd_icmp_type_value_cb },
  { "Address reply", NULL,                        ICMP_ADDRESSREPLY,   nd_icmp_type_value_cb },
  { "Custom",     N_("Custom type value"),        -1,                  nd_icmp_type_custom_cb },
  { NULL, NULL, 0, NULL}
};


ND_MenuData icmp_menu_unreach_code_data[] = {
  { "Network unreachable", NULL,                          ICMP_NET_UNREACH,    nd_icmp_code_value_cb },
  { "Host unreachable", NULL,                             ICMP_HOST_UNREACH,   nd_icmp_code_value_cb },
  { "Protocol unreachable", NULL,                         ICMP_PROT_UNREACH,   nd_icmp_code_value_cb },
  { "Port unreachable", NULL,                             ICMP_PORT_UNREACH,   nd_icmp_code_value_cb },
  { "Fragmentation needed", NULL,                         ICMP_FRAG_NEEDED,    nd_icmp_code_value_cb },
  { "Source route failed", NULL,                          ICMP_SR_FAILED,      nd_icmp_code_value_cb },
  { "Destination network unknown", NULL,                  ICMP_NET_UNKNOWN,    nd_icmp_code_value_cb },
  { "Destination host unknown", NULL,                     ICMP_HOST_UNKNOWN,   nd_icmp_code_value_cb },
  { "Destination host isolated", NULL,                    ICMP_HOST_ISOLATED,  nd_icmp_code_value_cb },
  { "Destination net administratively prohibited", NULL,  ICMP_NET_ANO,        nd_icmp_code_value_cb },
  { "Destination host administratively prohibited", NULL, ICMP_HOST_ANO,       nd_icmp_code_value_cb },
  { "Network unreachable for TOS", NULL,                  ICMP_NET_UNR_TOS,    nd_icmp_code_value_cb },
  { "Host unreachable for TOS", NULL,                     ICMP_HOST_UNR_TOS,   nd_icmp_code_value_cb },
  { "Communication prohibited by filtering", NULL,        ICMP_PKT_FILTERED,   nd_icmp_code_value_cb },
  { "Host precedence violated", NULL,                     ICMP_PREC_VIOLATION, nd_icmp_code_value_cb },
  { "Precedence cutoff", NULL,                            ICMP_PREC_CUTOFF,    nd_icmp_code_value_cb },
  { "Custom",     N_("Custom code value"),                -1,                  nd_icmp_code_custom_cb },
  { NULL, NULL, 0, NULL}
};

ND_MenuData icmp_menu_redirect_code_data[] = {
  { "Redirect for network", NULL,                   ICMP_REDIR_NET,      nd_icmp_code_value_cb },
  { "Redirect for host", NULL,                      ICMP_REDIR_HOST,     nd_icmp_code_value_cb },
  { "Redirect for TOS and network", NULL,           ICMP_REDIR_NETTOS,   nd_icmp_code_value_cb },
  { "Redirect for TOS and host", NULL,              ICMP_REDIR_HOSTTOS,  nd_icmp_code_value_cb },
  { "Custom",     N_("Custom code value"),          -1,                  nd_icmp_code_custom_cb },
  { NULL, NULL, 0, NULL}
};

ND_MenuData icmp_menu_time_ex_code_data[] = {
  { "TTL is zero during transit", NULL,             ICMP_EXC_TTL,        nd_icmp_code_value_cb },
  { "TTL is zero during reassembly", NULL,          ICMP_EXC_FRAGTIME,   nd_icmp_code_value_cb },
  { "Custom",     N_("Custom code value"),          -1,                  nd_icmp_code_custom_cb },
  { NULL, NULL, 0, NULL}
};

ND_MenuData icmp_menu_param_code_data[] = {
  { "IP header bad", NULL,                          0,                   nd_icmp_code_value_cb },
  { "Option missing", NULL,                         1,                   nd_icmp_code_value_cb },
  { "Custom",     N_("Custom code value"),          -1,                  nd_icmp_code_custom_cb },
  { NULL, NULL, 0, NULL}
};

ND_MenuData icmp_menu_data[] = {
  { N_("Fix Checksums"),  N_("Fixes the ICMP checksums of the current selection"), 0, nd_icmp_cksum_fix_cb },
  { NULL, NULL, 0, NULL}
};


static LND_Protocol *icmp;
static ND_Protocol  *icmp_gui;


/* Plugin hook implementations: ---------------------------------------- */

const char *
name(void)
{
  return _("ICMP Plugin");
}


const char *
description(void)
{
  return _("A plugin providing support for ICMPv4.\n");
}


const char *
author(void)
{
  return ("Christian Kreibich, <christian@whoop.org>");
}


const char *
version(void)
{
  return VERSION_MAJOR;
}


LND_Protocol *
init(void)
{
  if (! (icmp = libnd_proto_registry_find(LND_PROTO_LAYER_NET|LND_PROTO_LAYER_TRANS,
					  IPPROTO_ICMP)))
    return NULL;
  
  icmp_gui = nd_proto_new(icmp);

  icmp_gui->create_gui      = nd_icmp_create_gui;
  icmp_gui->set_gui         = nd_icmp_set_gui;

  /* We're using a button table to display the protocol content,
     so we need to hook them in here: */
  icmp_gui->fields = icmp_fields;
  icmp_gui->header_width = 32;

  /* We provide a little menu that allows us to fix checksums. */
  icmp_gui->menu = nd_gui_create_menu(icmp_menu_data);

  return icmp;
}


/* Protocol method implementations: ------------------------------------ */

GtkWidget *
nd_icmp_create_gui(LND_Trace *trace, LND_ProtoInfo *pinf)
{
  GtkWidget *table;

  table = nd_gui_proto_table_create(trace, pinf);

  return table;
}


void       
nd_icmp_set_gui(const LND_Packet *packet, LND_ProtoInfo *pinf)
{
  struct icmp *icmphdr;
  
  icmphdr = (struct icmp*) libnd_packet_get_data(packet, icmp, 0);

  nd_icmp_set_gui_type(pinf, icmphdr);
  nd_icmp_set_gui_code(pinf, icmphdr);
  nd_icmp_set_gui_cksum(pinf, icmphdr, packet);

  if (libnd_icmp_header_is_error(icmphdr))
    {
      nd_icmp_set_gui_data(pinf, icmphdr, packet);
      return;
    }

  switch (icmphdr->icmp_type)
    {
    case ICMP_ECHO:
    case ICMP_ECHOREPLY:
      nd_icmp_set_gui_echo(pinf, icmphdr, packet);
      break;

    case ICMP_TIMESTAMP:
    case ICMP_TIMESTAMPREPLY:
      nd_icmp_set_gui_timestamp(pinf, icmphdr, packet);
      break;

    case ICMP_INFO_REQUEST:
    case ICMP_INFO_REPLY:
      nd_icmp_set_gui_info(pinf, icmphdr, packet);
      break;

    case ICMP_ADDRESS:
    case ICMP_ADDRESSREPLY:
      nd_icmp_set_gui_addr(pinf, icmphdr, packet);
      break;

    case 9:
      nd_icmp_set_gui_router_adv(pinf, icmphdr, packet);
      break;

    case 10:
      nd_icmp_set_gui_data(pinf, icmphdr, packet);
      break;

    default:
      D(("Unhandled ICMP code\n"));
    }
}



/* Misc helper stuff below --------------------------------------------- */

void
nd_icmp_set_gui_type(LND_ProtoInfo *pinf, struct icmp *icmphdr)
{
  nd_proto_field_set_for_menu(pinf, &icmp_fields[0], DATA_TO_PTR(icmphdr->icmp_type),
			      icmp_menu_type_data, "%u");
}


void
nd_icmp_set_gui_code(LND_ProtoInfo *pinf, struct icmp *icmphdr)
{
  char            s[MAXPATHLEN];
  
  switch (icmphdr->icmp_type)
    {
    case ICMP_DEST_UNREACH:
      nd_proto_field_set_for_menu(pinf, &icmp_fields[1], DATA_TO_PTR(icmphdr->icmp_code),
				  icmp_menu_unreach_code_data, "%u");
      break;
    case ICMP_REDIRECT:
      nd_proto_field_set_for_menu(pinf, &icmp_fields[1], DATA_TO_PTR(icmphdr->icmp_code),
				  icmp_menu_redirect_code_data, "%u");
      break;
    case ICMP_TIME_EXCEEDED:
      nd_proto_field_set_for_menu(pinf, &icmp_fields[1], DATA_TO_PTR(icmphdr->icmp_code),
				  icmp_menu_time_ex_code_data, "%u");
      break;
    case ICMP_PARAMETERPROB:
      nd_proto_field_set_for_menu(pinf, &icmp_fields[1], DATA_TO_PTR(icmphdr->icmp_code),
				  icmp_menu_param_code_data, "%u");
      break;
    default:
      g_snprintf(s, MAXPATHLEN, "%u", icmphdr->icmp_code);
      nd_proto_field_set(pinf, &icmp_fields[1], s);
    }
}


void
nd_icmp_set_gui_cksum(LND_ProtoInfo *pinf,
		      struct icmp *icmphdr,
		      const LND_Packet *packet)
{
  nd_proto_field_set(pinf, &icmp_fields[2], DATA_TO_PTR(ntohs(icmphdr->icmp_cksum)));

  if (!libnd_icmp_message_complete(packet))
    {
      nd_proto_info_field_set_state(pinf, 
				    &icmp_fields[2],
				    ND_FIELD_STATE_UNKN);
      return;
    }
  
  if (!libnd_icmp_csum_correct(packet, NULL))
    nd_proto_info_field_set_state(pinf, 
				  &icmp_fields[2],
				  ND_FIELD_STATE_ERROR);
  else
    nd_proto_info_field_set_state(pinf,
				  &icmp_fields[2],
				  ND_FIELD_STATE_NORMAL);
}


void
nd_icmp_set_gui_data(LND_ProtoInfo *pinf,
		     struct icmp *icmphdr,
		     const LND_Packet *packet)
{
  char            s[MAXPATHLEN];
  guchar         *data;

  nd_gui_proto_table_clear(libnd_packet_get_trace(packet), pinf);
  
  data = ((guchar*) icmphdr) + 4;
  g_snprintf(s, MAXPATHLEN, "%.2x:%.2x:%.2x:%.2x", data[0], data[1], data[2], data[3]);	     
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_error_fields[0], s, FALSE);
}


void
nd_icmp_set_gui_echo(LND_ProtoInfo *pinf,
		     struct icmp *icmphdr,
		     const LND_Packet *packet)
{
  nd_gui_proto_table_clear(libnd_packet_get_trace(packet), pinf);

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[0],
			 DATA_TO_PTR(icmphdr->icmp_id), FALSE);
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[1],
			 DATA_TO_PTR(icmphdr->icmp_seq), FALSE);  
}


void
nd_icmp_set_gui_timestamp(LND_ProtoInfo *pinf,
			  struct icmp *icmphdr,
			  const LND_Packet *packet)
{
  nd_gui_proto_table_clear(libnd_packet_get_trace(packet), pinf);

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[0],
			 DATA_TO_PTR(icmphdr->icmp_id), FALSE);
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[1],
			 DATA_TO_PTR(icmphdr->icmp_seq), FALSE);  

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_tstamp_fields[0],
			 DATA_TO_PTR(ntohl(icmphdr->icmp_otime)), FALSE);

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_tstamp_fields[1],
			 DATA_TO_PTR(ntohl(icmphdr->icmp_rtime)), FALSE);  

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_tstamp_fields[2],
			 DATA_TO_PTR(ntohl(icmphdr->icmp_ttime)), FALSE);    
}


void
nd_icmp_set_gui_info(LND_ProtoInfo *pinf,
		     struct icmp *icmphdr,
		     const LND_Packet *packet)
{
  nd_gui_proto_table_clear(libnd_packet_get_trace(packet), pinf);

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[0],
			 DATA_TO_PTR(icmphdr->icmp_id), FALSE);
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[1],
			 DATA_TO_PTR(icmphdr->icmp_seq), FALSE);  
}


void
nd_icmp_set_gui_addr(LND_ProtoInfo *pinf,
		     struct icmp *icmphdr,
		     const LND_Packet *packet)
{
  nd_gui_proto_table_clear(libnd_packet_get_trace(packet), pinf);

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[0],
			 DATA_TO_PTR(icmphdr->icmp_id), FALSE);
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_req_seq_fields[1],
			 DATA_TO_PTR(icmphdr->icmp_seq), FALSE);  

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_addr_mask_fields[0],
			 inet_ntoa(icmphdr->icmp_gwaddr), FALSE);    
}


void       
nd_icmp_set_gui_router_adv(LND_ProtoInfo *pinf,
			   struct icmp *icmphdr,
			   const LND_Packet *packet)
{
  int i;
  nd_gui_proto_table_clear(libnd_packet_get_trace(packet), pinf);

  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_router_adv_fields[0],
			 DATA_TO_PTR(icmphdr->icmp_num_addrs), FALSE);
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_router_adv_fields[1],
			 DATA_TO_PTR(icmphdr->icmp_wpa), FALSE);
  nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_router_adv_fields[2],
			 DATA_TO_PTR(ntohs(icmphdr->icmp_lifetime)), FALSE);

  for (i = 0; i < icmphdr->icmp_num_addrs; i++)
    {
      guchar *data;
      struct in_addr *in;
      guint32 *prec;

      data = ((guchar *) (&icmphdr->icmp_lifetime + 1)) + i * 8;

      if (data + 8 > libnd_packet_get_end(packet))
	break;

      in = (struct in_addr *) data;
      prec = (guint32 *) (data + 4);

      nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_router_adv_fields[3],
			     inet_ntoa(*in), FALSE);
      nd_gui_proto_table_add(libnd_packet_get_trace(packet), pinf, &icmp_router_adv_fields[4],
			     DATA_TO_PTR(ntohl(*prec)), FALSE);
    }
}


LND_Protocol *
nd_icmp_get(void)
{
  return icmp;
}


ND_Protocol *
nd_icmp_get_gui(void)
{
  return icmp_gui;
}


gboolean
nd_icmp_header_is_error(struct icmp *icmphdr)
{
  if (!icmphdr)
    return FALSE;

  switch (icmphdr->icmp_type)
    {
    case ICMP_DEST_UNREACH:
    case ICMP_SOURCE_QUENCH:
    case ICMP_REDIRECT:
    case ICMP_TIME_EXCEEDED:
    case ICMP_PARAMETERPROB:
      return TRUE;
    }

  return FALSE;
}












